package com.common.user.service;

import java.security.Principal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;

import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import com.common.user.domain.Message;
import com.common.user.domain.User;
import com.common.exception.EmailNotFoundException;
import com.common.user.repository.UserRepository;
import com.common.util.MailUtils;

@Service
public class UserService {

	@Autowired
	private UserRepository userRepository;
	
	@Autowired
	PasswordEncoder BCryptPasswordEncoder;
	
	@Autowired
    private JavaMailSenderImpl sender;
	
	private Logger logger = LoggerFactory.getLogger(getClass());

	/**
	 * Get a user by username.
	 * @param username
	 * @return
	 */
	public User findByUsername(String username) {
		return userRepository.findByUsername(username);
	}
	
	/**
	 * Create or update a User.
	 * @param user
	 * @return
	 */
	public User save(User user) {
		return userRepository.save(user);
	}
	
	/**
	 * Register a user and send a random password by email.
	 * @param user
	 * @throws Exception 
	 */
	public void register(User user) throws Exception {
		
		String password = RandomStringUtils.randomAlphanumeric(8);
		
		user.setPassword(BCryptPasswordEncoder.encode(password));
		
		user = userRepository.save(user);
	
		//send mail
		if(user.getEmail() == null) {
			throw new EmailNotFoundException("The email of the user cannot be null or empy.");
		}
		MailUtils.sendTextWithHtml(sender,  new String[] {user.getEmail()}, "注册成功!", user.getUsername() + "，你的密码是：" + password + "。请及时修改。");	
		logger.info("系统向用户：" + user.getUsername() + '[' + user.getEmail() + ']' +"发送了注册成功邮件");
	}
	
	/**
	 * Get the current signin user by decode the jwt.
	 * @param request
	 * @return
	 */
	public User getCurrentUser(HttpServletRequest request) {
		
		Principal principal = request.getUserPrincipal();
		
		if(principal == null) {
			return null;
		}
		
		String username = principal.getName();
		
		if(username == null) {
			return null;
		}
		
		User user = userRepository.findByUsername(username);
		
		return user;
	}
	
	/**
	 * Reset user password.
	 * @param userInfo
	 * 			The username, email or phone of the user to reset password.
	 * @return
	 * @throws Exception 
	 */
	public Message resetPassword(String userInfo) throws Exception {
		List<User> users = queryInFields(userInfo);
		if(users.size() == 0) {
			return new Message("The specific user not found.");
		}
		
		if(users.size() > 1) {
			return new Message("The specific user has more than one entity.");
		}
		
		User user = users.get(0);
		String newPassword = RandomStringUtils.randomAlphanumeric(8);
		user.setPassword(BCryptPasswordEncoder.encode(newPassword));
		userRepository.save(user);
		
		//send email
		if(user.getEmail() == null) {
			throw new EmailNotFoundException("The email of the user cannot be null or empy.");
		}
		MailUtils.sendTextWithHtml(sender,  new String[] {user.getEmail()}, "密码重置成功!", user.getUsername() + "，你的新密码是：" + newPassword + "。请及时修改。");		
		logger.info("系统向用户：" + user.getUsername() + '[' + user.getEmail() + ']' +"发送了密码重置成功邮件");
		
		return new Message("Password reset success, please log in to your mailbox and get the new password.");
	}
	
	/**
	 * Modify the password of the current user.
	 * @param request
	 * @param newPassword
	 */
	public void updatePassword(HttpServletRequest request, String newPassword) {
		User user = getCurrentUser(request);
		user.setPassword(BCryptPasswordEncoder.encode(newPassword));
		userRepository.save(user);
	}
	
	/**
	 * Query users by multiple conditions. 
	 * @param allRequestParams
	 * @return
	 */
	public List<User> query(Map<String, String> allRequestParams){
		
		Specification<User> specification = new Specification<User>() {
			
			@Override
			public Predicate toPredicate(Root<User> root, 
										 CriteriaQuery<?> query, 
										 CriteriaBuilder cb) {
				
				List<Predicate> predicates = new ArrayList<>();
				
				if(allRequestParams.containsKey("username")) {
					predicates.add(cb.equal(root.<String>get("username"), allRequestParams.get("username")));
				}
				if(allRequestParams.containsKey("email")) {
					predicates.add(cb.equal(root.<String>get("email"), allRequestParams.get("email")));
				}
				if(allRequestParams.containsKey("name")) {
					predicates.add(cb.like(root.<String>get("name"), allRequestParams.get("name")));
				}
				if(allRequestParams.containsKey("phone")) {
					predicates.add(cb.equal(root.<String>get("phone"), allRequestParams.get("phone")));
				}
				
				return cb.and(predicates.toArray(new Predicate[predicates.size()]));
			}
		};
		return userRepository.findAll(specification);
	}
	
	/**
	 * List of users when the condition match username or email or phone.
	 * @param condition
	 * @return
	 */
	public List<User> queryInFields(String condition) {
		
		Set<User> userSet = new HashSet<>();
		Map<String, String> allParams = new HashMap<>();
		
		allParams.put("username", condition);
		userSet.addAll(query(allParams));
		
		allParams.clear();
		allParams.put("email", condition);
		userSet.addAll(query(allParams));
		
		allParams.clear();
		allParams.put("phone", condition);
		userSet.addAll(query(allParams));
		
		return new ArrayList<>(userSet);				
	}
}
